namespace OFX.Aggregator.Implementation.BESnet.Checking
{
	using System;
	using System.Collections.Generic;
	using System.Globalization;
	using System.Linq;
	using System.Text.RegularExpressions;

	using Infrastructure;

	using Microsoft.VisualBasic;

	public sealed class StatementInterpreter : StatementInterpreterBase
	{
		public StatementInterpreter(string culture, IRegexPatternProvider regexProvider)
			: base(culture, regexProvider) { }

		public override Statement InterpretStatement(string html)
		{
			const RegexOptions options = (RegexOptions.Singleline | RegexOptions.Multiline | RegexOptions.IgnoreCase);
			Match matchStatementDate = Regex.Match(html, regexProvider.GetMatchPattern("StatementDate"), options);
			if (!matchStatementDate.Success)
				throw new InterpretationFailedException(this, "Screen scraping failed: Failed to find/understand statement date.");

			DateTime dateStatement = DateTime.Parse(matchStatementDate.Groups[1].Value, null, DateTimeStyles.AssumeLocal);
			Match matchStatementHeader = Regex.Match(html, regexProvider.GetMatchPattern("StatementHeader"), RegexOptions.Singleline);
			if (!matchStatementHeader.Success)
				throw new InterpretationFailedException(this, "Screen scraping failed: Failed to find/understand statement header.");

			string acctNumber = matchStatementHeader.Groups["AccountNumber"].Value;
			string currency = matchStatementHeader.Groups["Currency"].Value;
			Match matchAvailableCredit = Regex.Match(html, regexProvider.GetMatchPattern("AvailableCredit"), options);
			if (!matchAvailableCredit.Success)
				throw new InterpretationFailedException(this, "Screen scraping failed: Failed to find/understand available credit on statement.");

			const double acctSpendingLimit = 0.0;
			double availableCredit = RetrieveMatchValueAsCurrency(matchAvailableCredit, "AvailableCredit");

			var statement = new Statement {Currency = currency, Date = dateStatement, Flags = StatementFlags.AccurateDate, Account = {BankId = "BES", Number = acctNumber, Type = AccountType.Checking, SpendingLimit = acctSpendingLimit, Balance = (availableCredit - acctSpendingLimit)}};

			var checkingAcct = new PaymentOriginator {Id = acctNumber};

			var reg = new Regex(regexProvider.GetMatchPattern("StatementLine"), options);
			MatchCollection statementLineMatches = reg.Matches(html);
			var statementLineToControlBalanceDictionary = new Dictionary<StatementLine, double>(statementLineMatches.Count);
			foreach (Match statementLineMatch in statementLineMatches)
			{
				DateTime datePosted = DateTime.Parse(RetrieveMatchValue(statementLineMatch, "datePosted"), null, DateTimeStyles.AssumeLocal);
				datePosted = datePosted.AddHours(12);
				DateTime dateValue = DateTime.Parse(RetrieveMatchValue(statementLineMatch, "dateValue"), null, DateTimeStyles.AssumeLocal);
				dateValue = dateValue.AddHours(12);

				string transactionType = RetrieveMatchValue(statementLineMatch, "transactionType");
				string description = RetrieveMatchValue(statementLineMatch, "description");
				double debitAmount = RetrieveMatchValueAsCurrency(statementLineMatch, "debitAmount");
				double creditAmount = RetrieveMatchValueAsCurrency(statementLineMatch, "creditAmount");
				double amount = (debitAmount == 0.0 ? creditAmount : -debitAmount);
				double controlBalance = RetrieveMatchValueAsCurrency(statementLineMatch, "controlBalance");

				if (description.ToUpper() == description)
					description = Strings.StrConv(description, VbStrConv.ProperCase, cultureInfo.LCID);

				var statementLine = new StatementLine {Amount = amount, DatePosted = datePosted, DateValue = dateValue, Description = description, Memo = description, TransactionType = TransactionType.POS};

				switch (transactionType)
				{
					case "LEV":
					case "DEB":
						statementLine.TransactionType = TransactionType.Debit;
						break;
					case "CRD":
						statementLine.TransactionType = TransactionType.Credit;
						break;
					case "SDD":
					case "PGT":
						statementLine.TransactionType = TransactionType.Payment;
						break;
					case "TRF":
						statementLine.TransactionType = TransactionType.Transfer;
						break;
					case "TPS":
						statementLine.TransactionType = TransactionType.POS;
						break;
				}

				statementLineToControlBalanceDictionary[statementLine] = controlBalance;

				checkingAcct.StatementLines.Add(statementLine);
			}

			createUniqueTransactionIds(checkingAcct, statementLineToControlBalanceDictionary);

			statement.PaymentOriginators.Add(checkingAcct);

			StatementInterpreterHelper.InferStatementDates(statement);

			return statement;
		}

		private static void createUniqueTransactionIds(PaymentOriginator checkingAcct, IDictionary<StatementLine, double> statementLineToControlBalanceDictionary)
		{
			DateTime firstLineDate = (from statementLine in checkingAcct.StatementLines select statementLine.DateValue).Min();
			IEnumerable<StatementLine> firstValueDateInStatement = from statementLine in checkingAcct.StatementLines where statementLine.DateValue == firstLineDate select statementLine;

			checkingAcct.StatementLines.RemoveAll(firstValueDateInStatement.Contains);

			foreach (var thisStatementLine in checkingAcct.StatementLines)
			{
				double controlBalance = statementLineToControlBalanceDictionary[thisStatementLine];
				List<StatementLine> linesInDateList = checkingAcct.StatementLines.Where(statementLine => statementLine.DateValue == thisStatementLine.DateValue).ToList();

				int index = linesInDateList.Count - linesInDateList.IndexOf(thisStatementLine);

				if (index == 1)
					thisStatementLine.TransactionId = string.Format("{0}.{1}", thisStatementLine.DateValue.ToUniversalTime().ToString("d", CultureInfo.InvariantCulture), controlBalance.ToString(CultureInfo.InvariantCulture));
				else
					thisStatementLine.TransactionId = string.Format("{0}({1}).{2}", thisStatementLine.DateValue.ToUniversalTime().ToString("d", CultureInfo.InvariantCulture), index, controlBalance.ToString(CultureInfo.InvariantCulture));
			}
		}
	}
}